package cn.trigram.json.serializer;

import cn.trigram.json.annotation.BigNumberFormat;
import cn.trigram.lang.BigMathUtil;
import cn.trigram.lang.ConvertUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.google.common.base.Joiner;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;

/**
 * 大数格式化
 *
 * @version 1.0.0
 * @date 2022/08/23 10:09:09
 * @since 1.0.0
 */
public class BigNumberContextualSerializer extends JsonSerializer<Number> implements ContextualSerializer {

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
        final BigNumberFormat format = Optional.ofNullable(property).map(p -> p.getAnnotation(BigNumberFormat.class)).orElse(null);
        final Class<? extends Number> rawClass = (Class<? extends Number>) Optional.ofNullable(property).map(BeanProperty::getType)
                .map(JavaType::getRawClass).orElse(null);
        if (Objects.equals(rawClass, BigDecimal.class)) {
            if (Objects.isNull(format) || format.useJackson()) {
                return new SoftReference<JsonSerializer<?>>(NumberSerializer.bigDecimalAsStringSerializer()).get();
            } else {
                return new SoftReference<JsonSerializer<?>>(new BigDecimalSerializer(format)).get();
            }
        } else {
            return new SoftReference<JsonSerializer<?>>(new NumberSerializer(rawClass)).get();
        }
    }

    @Override
    public void serialize(Number value, JsonGenerator gen, SerializerProvider serializers) {

    }

    /**
     * @version 1.0.0
     * @date 2022/08/02 16:09:59
     * @since 1.0.0
     */
    @JacksonStdImpl
    public static class BigDecimalSerializer extends StdSerializer<BigDecimal> {
        private final BigNumberFormat format;

        public BigDecimalSerializer(BigNumberFormat format) {
            super(BigDecimal.class, false);
            this.format = format;
        }

        @Override
        public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            if (Objects.isNull(value)) {
                gen.writeNull();
            }
            final String pattern = format.pattern();
            if (StringUtils.isNotBlank(pattern)) {
                final DecimalFormat decimalFormat = new DecimalFormat(pattern);
                decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
                gen.writeString(decimalFormat.format(value));
            } else {
                final int scale = format.scale();
                final boolean trailingZeros = format.stripTrailingZeros();
                final BigNumberFormat.Model model = format.model();
                if (model.equals(BigNumberFormat.Model.DEFAULT)) {
                    value = value.setScale(scale, RoundingMode.HALF_UP);
                    if (trailingZeros) {
                        value = value.stripTrailingZeros();
                    }
                    gen.writeString(value.toPlainString());
                } else if (model.equals(BigNumberFormat.Model.YUAN)) {
                    gen.writeString(value.setScale(2, RoundingMode.HALF_UP).toPlainString());
                } else if (model.equals(BigNumberFormat.Model.YUAN_THOUSANDS)) {
                    final DecimalFormat decimalFormat = new DecimalFormat(",##0.00");
                    decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
                    gen.writeString(decimalFormat.format(value));
                } else if (model.equals(BigNumberFormat.Model.TEN_THOUSAND_YUAN)) {
                    // 确保计算中有完整小数
                    value = value.setScale(128, RoundingMode.HALF_UP)
                            .divide(BigMathUtil.HUNDRED, RoundingMode.HALF_UP)
                            .divide(BigMathUtil.HUNDRED, RoundingMode.HALF_UP)
                            // 保留小数位
                            .setScale(scale, RoundingMode.HALF_UP);
                    if (trailingZeros) {
                        value = value.stripTrailingZeros();
                    }
                    gen.writeString(value.toPlainString());
                } else if (model.equals(BigNumberFormat.Model.TEN_THOUSAND_YUAN_THOUSANDS)) {
                    final DecimalFormat decimalFormat = new DecimalFormat(
                            Joiner.on('.').skipNulls()
                                    .join(",##0", StringUtils.trimToNull(StringUtils.repeat(trailingZeros ? '#' : '0', scale)))
                    );
                    decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
                    gen.writeString(
                            decimalFormat
                                    .format(
                                            value.setScale(128, RoundingMode.HALF_UP)
                                                    .divide(BigMathUtil.HUNDRED, RoundingMode.HALF_UP)
                                                    .divide(BigMathUtil.HUNDRED, RoundingMode.HALF_UP)
                                    )
                    );
                } else if (model.equals(BigNumberFormat.Model.PERCENTAGE)) {
                    value = value.multiply(BigMathUtil.HUNDRED).setScale(scale, RoundingMode.HALF_UP);
                    if (trailingZeros) {
                        value = value.stripTrailingZeros();
                    }
                    gen.writeString(value.toPlainString());
                } else if (model.equals(BigNumberFormat.Model.PERCENTAGE_WITH_SYMBOL)) {
                    value = value.multiply(BigMathUtil.HUNDRED).setScale(scale, RoundingMode.HALF_UP);
                    if (trailingZeros) {
                        value = value.stripTrailingZeros();
                    }
                    gen.writeString(value.toPlainString() + "%");
                } else if (model.equals(BigNumberFormat.Model.CUSTOM)) {
                    value = value.setScale(scale, RoundingMode.HALF_UP);
                    if (trailingZeros) {
                        value = value.stripTrailingZeros();
                    }
                    gen.writeString(value.toPlainString());
                } else {
                    gen.writeString(ConvertUtil.toStr(value));
                }
            }

        }

        @Override
        public void serializeWithType(BigDecimal value, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSer) throws IOException {
            final WritableTypeId typeId = typeSer.writeTypePrefix(gen, typeSer.typeId(value, JsonToken.VALUE_STRING));
            serialize(value, gen, provider);
            typeSer.writeTypeSuffix(gen, typeId);
        }

        @Override
        public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
            return createSchemaNode("string", true);
        }

        @Override
        public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
            visitStringFormat(visitor, typeHint);
        }

    }
}
